﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Brainee.NeuralNetwork;

namespace BrainUI
{
  public partial class ANN : Form
  {
    private Network _ann;
    private ITrainingMethod _trainer = new BackPropagationTraining2 ();
    private List<double> _errors;
    private TrainingSet _trainingSet;
    private List<Example> _exampleList;

    public ANN ()
    {
      InitializeComponent ();

      _exampleList = new List<Example> () {
        new Example( "XOR problem",
          new Network (2) {
            new Layer(new SigmoidFunction (), 2),
            new Layer(new SigmoidFunction (), 1),
          },
          new TrainingSet () {
            new TrainingItem(new double [2] { 0, 0 }, new double [1] { 0 }),
            new TrainingItem(new double [2] { 0, 1 }, new double [1] { 1 }),
            new TrainingItem(new double [2] { 1, 0 }, new double [1] { 1 }),
            new TrainingItem(new double [2] { 1, 1 }, new double [1] { 0 }),
          }),

        new Example( "424 encoder",
          new Network (4) {
            new Layer(new SigmoidFunction (), 2),
            new Layer(new SigmoidFunction (), 4),
          },
          new TrainingSet () {
            new TrainingItem(new double [4] { 1, 0, 0, 0 }, new double [4] { 1, 0, 0, 0 }),
            new TrainingItem(new double [4] { 0, 1, 0, 0 }, new double [4] { 0, 1, 0, 0 }),
            new TrainingItem(new double [4] { 0, 0, 1, 0 }, new double [4] { 0, 0, 1, 0 }),
            new TrainingItem(new double [4] { 0, 0, 0, 1 }, new double [4] { 0, 0, 0, 1 }),
          }),
      };

      foreach (Example e in _exampleList)
        _examples.Items.Add (e);
      _examples.SelectedIndex = 0;

      _learningRate.Text = _trainer.LearningRate.ToString ();
      _momentum.Text = _trainer.Momentum.ToString ();
      _maxIter.Text = _trainer.MaxIterations.ToString ();
      _minError.Text = _trainer.MinError.ToString ();
      _iteration.Text = _trainer.Iterations.ToString ();
      _error.Text = _trainer.Error.ToString ();

      UpdateSetView ();
    }

    private void UpdateSetView ()
    {
      _setView.Items.Clear ();
      if (_trainingSet == null)
        return;
      foreach (TrainingItem item in _trainingSet)
      {
        string input = ArrayToString (item.Input);
        string expected = ArrayToString (item.Output);
        string output = ArrayToString (_ann.Compute (item.Input));
        _setView.Items.Add (new ListViewItem (new string [] {
          input,
          expected,
          output
        }));
      }
    }

    private static string ArrayToString (double[] array)
    {
      StringBuilder sb = new StringBuilder ();
      sb.Append ("[ ");
      foreach (double d in array)
        sb.AppendFormat ("{0} ", d.ToString("0.00"));
      sb.Append ("]");

      return sb.ToString ();
    }

    private void startButton_CheckedChanged (object sender, EventArgs e)
    {
      if (_startButton.Checked)
      {
        _startButton.Enabled = false;

        double dvalue = 0.0;
        uint ivalue = 0;

        if (!double.TryParse (_learningRate.Text, out dvalue))
          return;
        _trainer.LearningRate = dvalue;

        if (!double.TryParse (_momentum.Text, out dvalue))
          return;
        _trainer.Momentum = dvalue;

        if (!uint.TryParse (_maxIter.Text, out ivalue))
          return;
        _trainer.MaxIterations = ivalue;

        if (!double.TryParse (_minError.Text, out dvalue))
          return;
        _trainer.MinError = dvalue;

        _worker.RunWorkerAsync ();

        _startButton.Enabled = true;
        _startButton.Text = "Stop";
      }
      else
      {
        _startButton.Enabled = false;

        if (_worker.IsBusy)
        {
          _worker.CancelAsync ();
        }

        _startButton.Enabled = true;
        _startButton.Text = "Start";
      }
    }

    private void errorGraph_Paint (object sender, PaintEventArgs e)
    {
      e.Graphics.FillRectangle (SystemBrushes.ButtonHighlight, e.ClipRectangle);

      if (_errors != null)
      {
        double[] localErrors = null;
        lock (_errors)
        {
          localErrors = _errors.ToArray ();
        }

        if (localErrors.Length > 1)
        {
          int drawStep = 1;
          while (localErrors.Length > _errorGraph.Width * drawStep)
            drawStep *= 2;

          // error[0] => 0,9 => 0,1
          // error[i] => x = error[i] / error[0] * 0,9 * 
          int previous = (int)(_errorGraph.Height * 0.1f);

          int x = 1;
          for (int i = drawStep; i < localErrors.Length; i += drawStep)
          {
            int current = (int)(_errorGraph.Height * (1 - 0.9 * (localErrors [i] / localErrors [0])));
            e.Graphics.DrawLine (SystemPens.ControlDark, x - 1, previous, x, current);

            x++;
            previous = current;
          }

          //float last = _errorGraph.Height * (1.0f - 0.9f * (float)(localErrors [localErrors.Count - 1] / localErrors [0]));
          //e.Graphics.DrawLine (SystemPens.ControlDark, (float)(localErrors.Count - 1) / drawStep, previous, (float)localErrors.Count / drawStep, last);
        }
      }
    }

    private void worker_DoWork (object sender, DoWorkEventArgs e)
    {
      _errors = new List<double> ();

      _trainer.Progress += delegate (object s, TrainingProgressEventArgs ea)
      {
        ea.Cancel = _worker.CancellationPending;

        lock (_errors)
        {
          _errors.Add (ea.Error);
        }

        if (_trainer.Iterations % 1000 == 0)
        {
          _iteration.Invoke (new MethodInvoker (() => _iteration.Text = ea.Iterations.ToString ()));
          _error.Invoke (new MethodInvoker (() => _error.Text = ea.Error.ToString ()));

          _errorGraph.Invalidate ();
        }
      };

      _ann.Randomize ();

      _trainer.Train (_ann, _trainingSet);
    }

    private void worker_RunWorkerCompleted (object sender, RunWorkerCompletedEventArgs e)
    {
      _startButton.Invoke (new MethodInvoker (() => _startButton.Checked = false));

      _iteration.Invoke (new MethodInvoker (() => _iteration.Text = _trainer.Iterations.ToString ()));
      _error.Invoke (new MethodInvoker (() => _error.Text = _trainer.Error.ToString ()));

      _errorGraph.Invalidate ();

      UpdateSetView ();
    }

    private void _examples_SelectedIndexChanged (object sender, EventArgs e)
    {
      Example exemple = (Example)_examples.SelectedItem;
      _ann = exemple.Network;
      _trainingSet = exemple.TrainingSet;

      _netView.Network = _ann;
      UpdateSetView ();
    }
  }
}
